#include "server.h"

//定义一个1024个元素的事件结构体数组
struct epoll_event evtArr[SOCKET_COUNT];
int listenFd;

//创建监听套接字
int initTcpSocket(char * pIP, u_int16_t port)
{
     unsigned int ip;
     //把字符串IP转为网络（大端）字节序
     inet_pton(AF_INET,pIP,&ip);
     struct sockaddr_in addr;
     addr.sin_family = AF_INET;//IPV4地址族
     addr.sin_port = htons(port);//转成网络字节序的端口号
     addr.sin_addr.s_addr = ip;//网络字节序IP地址

     // 如果要通信，就需要创建socket，返回SOCKET的文件描述符
     //参数1：地址族IPV4
     //参数2：流式SOCKET(tcp协议)
     //参数3：自动网络协议
     int fd = socket(AF_INET,SOCK_STREAM,0);
     int opt = 1 ;
     //目的是让服务器结束后，端口能立马使用
     if (setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt))<0)
     {
          printf("设置失败！\n");
     }
     //绑定电脑上的一个端口
     //参数1：socket
     //参数2：网络地址，为了满足历史遗留问题，需要把地址转为struct sockaddr *
     //参数3：结构体的长度
     bind(fd,(struct sockaddr *)&addr,sizeof(addr));

     //启动监听，监听有没有人连链接我，最多允许5个客户端同时排队连接，超过的直接返回连接错误
     listen(fd,5);
     return fd;
}



//创建监听套接字，创建epoll对象，把监听套接字加入到epoll中并返回epoll对象
int initServer(char * pIP, u_int16_t port)
{
     //初始化监听套接字
     listenFd = initTcpSocket(pIP,port);
     //epoll类似于select，是帮助我们检测套接字的
     //创建一个epoll对象，参数为epoll对象要管理的套接字数量
     int epObj = epoll_create(SOCKET_COUNT);

     //定义一个事件结构体，里面需要填写我们关心的事件，例如可读事件/可写事件等。。。
     struct epoll_event ee;
     ee.events = EPOLLIN;//填写我们关心的事件，EPOLLIN是可读事件
     ee.data.fd = listenFd;//填写需要关心可读事件的套接字，这里填写的是监听套接字
     //把监听套接字和它对应的事件结构体加入到EPOLL对象中进行管理（检测）
     epoll_ctl(epObj,EPOLL_CTL_ADD,listenFd,&ee);
     return epObj;
}

//pFun就代表外面出入的work函数，因为pFun保存了work的地址，所以调用外面的work只需要调用pFun就可以了
//C语言规定当一个指针指向了函数，那么这个指针的用法和指向的函数是一样的，用这个指针就是用那个指向的函数
void handleEvent(int epObj,void (*pAddClient)(int,char*,int), void (*pFun)(int,char *))
{
     while (1)
     {
          //epoll_wait等待epObj所管理的套接字上发生了感兴趣事件，如果某个套接字发生了对应的事件，epoll_wait会把
          //该套接字对应的事件结构体存入到第二个参数的数组中
          //返回值：返回当前epoll对象中有多少个fd发生了可读事件，也就此时有多少个fd对应的结构体被计入到了第二个参数的数组中
          int ret = epoll_wait(epObj,evtArr,SOCKET_COUNT,-1);

          for (size_t i = 0; i < ret; i++)
          {
               if(evtArr[i].data.fd == listenFd)//如果套接字等于监听套接字，需要接受客户端链接
               {
                    //用来存放客户端的地址
                    struct sockaddr_in client_addr;
                    //客户端的地址长度
                    socklen_t len = sizeof(client_addr);
                    //接受客户端的连接并返回新的用来和客户端通信的套接字
                    //因为fdNew是一个通信套接字，它上面也有可能产生可读事件。例如：客户端发消息过来
                    //所以也需要把它放到集合里让select监控
                    int fdNew = accept(listenFd,(struct sockaddr *)&client_addr,&len);
                    if(fdNew == -1)
                    {
                         printf("客户端连接错误\n");
                    }
                    else
                    {
                         // //把新的用来通信的套接字加入到数组中保存起来，用于后续群聊转发消息
                         // clientFdArr[connected] = fdNew;
                         // //连接数加1
                         // connected++;
                         // pAddClient(fdNew,1);
                         //定义一个事件结构体，里面需要填写我们关心的事件，例如可读事件/可写事件等。。。
                         struct epoll_event ee;
                         ee.events = EPOLLIN;//填写我们关心的事件，EPOLLIN是可读事件
                         ee.data.fd = fdNew;//填写需要关心可读事件的套接字，这里填写的是监听套接字
                         //把监听套接字和它对应的事件结构体加入到EPOLL对象中进行管理（检测）
                         epoll_ctl(epObj,EPOLL_CTL_ADD,fdNew,&ee);                         
                    }
               }
               else//通信套接字
               {
                    char buf[MSG_BUF_SIZE];
                    int ret = recv(evtArr[i].data.fd,buf,MSG_BUF_SIZE,0);
                    //说明客户端被关闭了
                    if(ret == 0)
                    {
                         //因为客户端已经被关闭，所以这边也需要被关闭
                         close(evtArr[i].data.fd);

                         //定义一个事件结构体，里面需要填写我们关心的事件，例如可读事件/可写事件等。。。
                         struct epoll_event ee;
                         ee.events = EPOLLIN;//填写我们关心的事件，EPOLLIN是可读事件
                         ee.data.fd = evtArr[i].data.fd;//填写需要关心可读事件的套接字，这里填写的是监听套接字
                         //要从epoll中移除掉该套接字
                         //把监听套接字和它对应的事件结构体加入到EPOLL对象中进行管理（检测）
                         epoll_ctl(epObj,EPOLL_CTL_DEL,evtArr[i].data.fd,&ee);

                         pAddClient(evtArr[i].data.fd,NULL,0);

                         printf("有一个客户端被关了，我们把它的套接字关闭，同时从epoll中移除了它\n");
                    }
                    else
                    {
                        pFun(evtArr[i].data.fd,buf);//调用外面传入的work函数 
                    }
                    // printf("revc返回值为:%d\n",ret);
                    
               }
               // printf("套接字%d发生了EPOLLIN事件,我从事件结构体中找到的套接字fd\n",evtArr[i].data.fd);
          }
     }
}